package cn.zwx.learn.basic.redis_lock;

import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.sync.RedisCommands;
import org.checkerframework.checker.units.qual.A;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @projectName:java-basic
 * @see:cn.zwx.learn.basic.redis_lock
 * @author:zhangwenxue
 * @createTime:2021/3/24 9:54
 * @version:1.0
 */
@Component
public class MyRedisLock {

    private Logger logger = LoggerFactory.getLogger(MyRedisLock.class);
    /**
     * @description 线程状态
     **/
    private final Boolean IS_OK = Boolean.TRUE;

    /**
     * @description 字符集
     **/
    private static final String UTF_8 = "UTF-8";
    /**
     * @description 释放Redis锁Lua脚本
     **/
    public static final String UNLOCK_LUA;

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * @description 存储获取锁的线程集合对象
     **/
    private  ConcurrentMap<Thread, ContinueLifeTheadData> THREAD_DATA;



    /**
     * 释放锁脚本，原子操作
     */
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"GET\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"DEL\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    public MyRedisLock() {
        this.THREAD_DATA = new ConcurrentHashMap<>();;
    }



    /**
     * @description 获取Redis锁
     * @createTime 2021/3/24 10:10
     **/
    public boolean getLock(String lockKey,String lockValue,Long expireTime, TimeUnit timeUnit) {
        //判断当前线程是否已经持有锁
        Thread currentThread = Thread.currentThread();
        ContinueLifeTheadData continueLifeTheadData = THREAD_DATA.get(currentThread);
        if (continueLifeTheadData != null) {
            // 如果存在则并且是同一个线程则+1一次从入次数
            continueLifeTheadData.lockCount.incrementAndGet();
            return true;
        } else {
            // 如果不存在锁,则去获取锁
            RedisCallback<Boolean> callback = (connection) ->
                    connection.set(lockKey.getBytes(Charset.forName(UTF_8)), lockValue.getBytes(Charset.forName(UTF_8)), Expiration.seconds(timeUnit.toSeconds(expireTime)), RedisStringCommands.SetOption.SET_IF_ABSENT);
            Boolean execute = (Boolean) redisTemplate.execute(callback);
            if (IS_OK.equals(execute)) {
                // 设置续命线程为守护线程 动态去给到时间没有释放的线程续命
                ContinueLifeThead continueLifeThead = new ContinueLifeThead(lockKey, lockValue, expireTime,TimeUnit.SECONDS, redisTemplate);
                Thread daemonThread = new Thread(continueLifeThead);
                daemonThread.setDaemon(true);
                daemonThread.start();
                ContinueLifeTheadData newContinueLifeTheadData = new ContinueLifeTheadData(currentThread, lockKey, lockValue, daemonThread, continueLifeThead);
                THREAD_DATA.put(currentThread, newContinueLifeTheadData);
                return true;
            } else {
                return false;
            }
        }
    }

   /**
    * @description 释放锁
    * @return void
    * @author zhangwenxue
    * @createTime 2021/3/24 10:22
    **/
    public void release(String lockKey,String lockValue) {
        Thread currentThread = Thread.currentThread();
        ContinueLifeTheadData continueLifeTheadData = THREAD_DATA.get(currentThread);
        if (continueLifeTheadData == null) {
            throw new IllegalMonitorStateException("You do not own the lock");
        } else {
            int lockCount = continueLifeTheadData.lockCount.decrementAndGet();
            if (lockCount <= 0) {
                if (lockCount < 0) {
                    throw new IllegalMonitorStateException("Lock count has gone negative for lock");
                } else {
                    //当 lockCount 等于0时，代表所有重入情况释放完毕，此时可以结束续费线程，并释放锁
                    try {
                        //停止续费线程
                        continueLifeTheadData.continueLifeThead.stop();
                        continueLifeTheadData.daemonThread.interrupt();
                        //释放锁
                        RedisCallback<Boolean> callback = (connection) ->
                                connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1, lockKey.getBytes(Charset.forName(UTF_8)), lockValue.getBytes(Charset.forName(UTF_8)));
                        Boolean execute = (Boolean) redisTemplate.execute(callback);
                        if (execute){
                            logger.info("---locked release success---key:{},value:{}",lockKey,lockValue);
                        }else {
                            logger.info("---locked release failed ---key:{},value:{}",lockKey,lockValue);
                        }
                    } finally {
                        THREAD_DATA.remove(currentThread);
                    }
                }
            } else {
                logger.info("---relocked release---key:{},value:{}",lockKey,lockValue);
            }
        }
    }



}
